import { lastValueFrom } from "rxjs";
import { Injectable } from "@nestjs/common";

import {
  User,
  RestDto,
  Response,
  ENUM_APP,
  ENUM_ERROR,
  UserRpcInterface,
  isSameMasterRpcDto,
  UserRpcGetMasterPath,
  getUserInfoByIdRpcDto,
  UserRpcIsSameMasterPath,
  UserRpcGetFirstUserPath,
  UserRpcGetUserInfoByIdPath,
  getUserInfoBySessionIdRpcDto,
  UserRpcGetUserInfoBySessionIdPath,
  UserRpcGetDeriveUserListPath,
} from "qqlx-core";
import { checkResponse, getDefaultResponse, isStringNotValid } from "qqlx-cdk";

import { BusRpc } from "./app.droplet-bus";
import { RpcWaiting } from "./rpc.response";
import { RpcConnection } from "./com.connection";

@Injectable()
export class UserRpc extends RpcConnection implements UserRpcInterface<RestDto> {
  constructor(readonly BusRpc: BusRpc) {
    super(BusRpc, ENUM_APP.STREAM_USER);
  }

  @RpcWaiting(UserRpcGetUserInfoByIdPath)
  async getUserInfoById(dto: RestDto & getUserInfoByIdRpcDto) {
    const { uuid32 } = dto;
    if (isStringNotValid(uuid32)) throw { code: ENUM_ERROR.NOT_FOUND_USER };

    const ob = this.CLIENT.send(UserRpcGetUserInfoByIdPath, dto);
    return lastValueFrom<User>(ob);
  }

  @RpcWaiting(UserRpcGetUserInfoBySessionIdPath, { logger: false })
  async getUserInfoBySessionId(dto: getUserInfoBySessionIdRpcDto) {
    const { SessionId } = dto;
    if (isStringNotValid(SessionId)) throw { code: ENUM_ERROR.AUTHORIZATION_NONE };

    const ob = this.CLIENT.send(UserRpcGetUserInfoBySessionIdPath, dto);
    return lastValueFrom<Pick<User, "uuid32" | "type">>(ob);
  }

  @RpcWaiting(UserRpcGetFirstUserPath, { logger: false })
  async getFirstUser() {
    const ob = this.CLIENT.send(UserRpcGetFirstUserPath, {});
    return lastValueFrom<Pick<User, "uuid32">>(ob);
  }

  private userMasterCache = new Map<User["uuid32"], { master_user_id: string; time_expire: number }>();
  private userMasterCacheTime = 1000 * 60 * 60; // 1 hour

  @RpcWaiting(UserRpcGetMasterPath, { logger: false })
  async getMaster(dto: RestDto) {
    const { visitorId } = dto;
    if (isStringNotValid(visitorId)) throw { code: ENUM_ERROR.NOT_FOUND_USER };

    const responseValue = getDefaultResponse<Pick<User, "uuid32">>();
    const cache = this.userMasterCache.get(visitorId);

    if (cache && cache.time_expire > Date.now()) {
      responseValue.data = { uuid32: cache.master_user_id };
    } else {
      const ob = this.CLIENT.send(UserRpcGetMasterPath, dto);
      const userRpcRes = await lastValueFrom<Response<Pick<User, "uuid32">>>(ob);
      const { uuid32 } = checkResponse<Pick<User, "uuid32">>(userRpcRes);

      responseValue.data = { uuid32 };
      const time_expire = Date.now() + this.userMasterCacheTime;
      this.userMasterCache.set(visitorId, { master_user_id: uuid32, time_expire });
    }

    responseValue.code = ENUM_ERROR.SUCCESS;
    return responseValue as unknown as Pick<User, "uuid32">;
  }

  @RpcWaiting(UserRpcGetDeriveUserListPath)
  async getDeriveUserList(dto: RestDto) {
    const { visitorId } = dto;
    if (isStringNotValid(visitorId)) throw { code: ENUM_ERROR.NOT_FOUND_USER };

    const ob = this.CLIENT.send(UserRpcGetDeriveUserListPath, dto);
    return lastValueFrom<User[]>(ob);
  }

  @RpcWaiting(UserRpcIsSameMasterPath, { logger: false })
  async isSameMaster(dto: RestDto & isSameMasterRpcDto): Promise<void> {
    const ob = this.CLIENT.send(UserRpcIsSameMasterPath, dto);
    return lastValueFrom<void>(ob);
  }
}
